// Copyright 2010, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_TIMELABEL_H_
#define LIBV_TIMELABEL_H_


#include <stdio.h>

#ifdef _WIN32
# include <libv/windows_lean.h>
typedef __int64 int64_t;
#else
# include <sys/time.h>
# include <stdint.h>
#endif



namespace libv
{

/** Класс для создания временных меток и подсчета разности между ними.
  * Разность между двумя метками представлена прокси-классом TimeDefference.
  * Метка представляет собой указатель на момент времени,
  * в то время как интервал — расстояние между двумя метками.
  * Возможные операции:
  *   - метка - метка == интервал
  *   - метка + интервал == метка
  *   - метка - интервал == метка
  *   - интервал - интервал == интервал
  *   - интервал + интервал == интервал.
  * Оба класса поддерживают сложные присваивания (-=, +=) и операции сравнения.
  * Возможен перевод интервалов в секунды.
  *
  * Точность меток определяется предоставленными операционной системой
  * системными вызовами (в windows: 10**-9, в линукс: 10**-6).
  *
  * Примеры использования: краткий вариант.
  * @code
  * TimeLabel start;
  * // рабочий код //
  * start.PrintDiff("Code time");
  * @endcode
  *
  * Полный вариант.
  * @code
  * TimeLabel start;
  * // рабочий код //
  * TimeLabel finish;
  * printf("Code time: %g\n", (finish-start).seconds());
  * @endcode
  *
  * @exception не генерирует.
  * @warning класс потокобезопасен. */
class TimeLabel
{
  public:
    /** Разность между двумя метками TimeLabel (временной интервал). */
    class TimeDifference
    {
      friend class TimeLabel;
      public:
        TimeDifference() : diff_value_(0) {}

        /** Конструктор интервала от секунд. */
        explicit TimeDifference(const double secs)
            : diff_value_(static_cast<int64_t>(
                              secs * CurrentOSSecondsToTicksMultiplier())) {}


        /** Значение интервала в секундах. */
        double Seconds() const
        {
            return static_cast<double>(this->diff_value_) /
                CurrentOSSecondsToTicksMultiplier();
        }

        /** Вывод опциональной строки и значения интервала в секундах. */
        void Print(const char* const msg = "") const
        {
            printf("%s: %g\n", msg, Seconds());
        }

        /** Обнуление. */
        void Reset()
        {
            this->diff_value_ = 0;
        }

        TimeDifference& operator+=(const TimeDifference& rhv)
        {
            this->diff_value_ += rhv.diff_value_;
            return *this;
        }

        TimeDifference& operator-=(const TimeDifference& rhv)
        {
            this->diff_value_ -= rhv.diff_value_;
            return *this;
        }


        const TimeDifference operator+(const TimeDifference& rhv) const
        {
            TimeDifference tmp(*this);
            tmp += rhv;
            return tmp;
        }

        const TimeDifference operator-(const TimeDifference& rhv) const
        {
            TimeDifference tmp(*this);
            tmp -= rhv;
            return tmp;
        }


        bool operator<(const TimeDifference& rhv) const
        {
            return this->diff_value_ < rhv.diff_value_;
        }

        bool operator==(const TimeDifference& rhv) const
        {
            return this->diff_value_ == rhv.diff_value_;
        }

        bool operator!=(const TimeDifference& rhv) const
        {
            return !(*this == rhv);
        }

      private:
        /** Конструктор от временной метки. */
        explicit TimeDifference(const int64_t dv) : diff_value_(dv) {}

        int64_t CurrentOSSecondsToTicksMultiplier() const
        {
#ifdef _WIN32
            LARGE_INTEGER qpfreq;
            QueryPerformanceFrequency(&qpfreq);
            return qpfreq.QuadPart;
#else
            return 1e6;
#endif
        }


        /** Значение разности меток (платформозависимое). */
        int64_t diff_value_;
    };



    /** Создание новой временной метки. */
    TimeLabel() : label_value_(CurLabelValue()) {}


    /** Обновление временной метки до текущего значения. */
    void Update()
    {
        this->label_value_ = CurLabelValue();
    }

    double Seconds() const
    {
        return TimeDifference(CurLabelValue() - this->label_value_).Seconds();
    }

    /** Вывод интервала от метки до текущего момента. */
    void PrintDiff(const char* const msg = "") const
    {
        printf("%s: %g\n", msg, Seconds());
    }


    const TimeDifference operator-(const TimeLabel& rhv) const
    {
        return TimeLabel::TimeDifference(this->label_value_ - rhv.label_value_);
    }


    TimeLabel& operator-=(const TimeDifference& rhv)
    {
        this->label_value_ -= rhv.diff_value_;
        return *this;
    }

    TimeLabel& operator+=(const TimeDifference& rhv)
    {
        this->label_value_ += rhv.diff_value_;
        return *this;
    }

    const TimeLabel operator-(const TimeDifference& rhv)
    {
        TimeLabel tmp(*this);
        tmp -= rhv;
        return tmp;
    }

    const TimeLabel operator+(const TimeDifference& rhv)
    {
        TimeLabel tmp(*this);
        tmp += rhv;
        return tmp;
    }


    bool operator<(const TimeLabel& rhv) const
    {
        return this->label_value_ < rhv.label_value_;
    }

    bool operator==(const TimeLabel& rhv) const
    {
        return this->label_value_ == rhv.label_value_;
    }

    bool operator!=(const TimeLabel& rhv) const
    {
        return !(*this == rhv);
    }


  private:

    /** Текущее время (единицы измерения зависят от платформы). */
    int64_t CurLabelValue() const
    {
        int64_t result = -1;

#ifdef _WIN32
        LARGE_INTEGER qpcnt;
        if ( QueryPerformanceCounter(&qpcnt) == 0 )
            perror("QueryPerformanceCounter");
        else
            result = qpcnt.QuadPart;
#else
        /* Результат в микросекундах. */
        struct timeval tv;
        if ( gettimeofday(&tv, NULL) == -1 )
            perror("gettimeofday");
        else
            result = static_cast<int64_t>(1000000) *
                     static_cast<int64_t>(tv.tv_sec) +
                     static_cast<int64_t>(tv.tv_usec);
#endif
        return result;
    }


    /** Значение метки (платформозависимое). */
    int64_t label_value_;
};

}  // libv

#endif  // LIBV_TIMELABEL_H_
